/**
 * @file asthtml.ts 
 * @description 用于解析 html 文本为 AST
 */
import fs from "node:fs";

/**
 * 读取文件 返回 buf 就是包装了 fs.readFileSync;
 * @param fileName 
 * @returns 
 */
export function readFileAsString(fileName: string): string | undefined {
    let buffer: Buffer;
    try {
        buffer = fs.readFileSync(fileName);
    }
    catch (e) {
        return undefined;
    }
    let len = buffer.length;
    if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
        // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js,
        // flip all byte pairs and treat as little endian.
        len &= ~1; // Round down to a multiple of 2
        for (let i = 0; i < len; i += 2) {
            const temp = buffer[i];
            buffer[i] = buffer[i + 1];
            buffer[i + 1] = temp;
        }
        return buffer.toString("utf16le", 2);
    }
    if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
        // Little endian UTF-16 byte order mark detected
        return buffer.toString("utf16le", 2);
    }
    if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
        // UTF-8 byte order mark detected
        return buffer.toString("utf8", 3);
    }
    // Default is UTF-8 with no byte order mark
    return buffer.toString("utf8");
}

// see https://www.w3.org/TR/xml/#NT-S
const WhiteSpace = [0x20, 0x09, 0x0D, 0x0A];
// see https://www.w3.org/TR/xml/#NT-NameStartChar
const NameStartChar = [97, 122, 65, 90, 95, 95, 58, 58, 0xC0, 0xD6, 0xD8, 0xF6, 0xF8, 0x2FF, 0x370, 0x37D,
    0x37F, 0x1FFF, 0x200C, 0x200D, 0x2070, 0x218F, 0x2C00, 0x2FEF, 0x3001, 0xD7FF, 0xF900, 0xFDCF,
    0xFDF0, 0xFFFD, 0x10000, 0xEFFFF];
// see https://www.w3.org/TR/xml/#NT-NameChar
const NameChar = [97, 122, 65, 90, 48, 57, 95, 95, 58, 58, 45, 45, 46, 46, 0xB7, 0xB7,
    0xC0, 0xD6, 0xD8, 0xF6, 0xF8, 0x2FF, 0x203F, 0x2040, 0x300, 0x37D,
    0x37F, 0x1FFF, 0x200C, 0x200D, 0x2070, 0x218F, 0x2C00, 0x2FEF,
    0x3001, 0xD7FF, 0xF900, 0xFDCF, 0xFDF0, 0xFFFD, 0x10000, 0xEFFFF];
/**
 * 代码在文件中定位,注意:定位是字符定位,不是字节定位
 */
export interface TokenLocation {
    startLine: number;   // 开始行号，从1开始计算
    startCol: number;    // 开始行内位置，从1开始计算
    startPos: number; // 开始位置，从0开始计算，指字符位置，不是字节位置
    endLine: number;     // 结束行号（包含），从1开始计算
    endCol: number;      // 结束行内位置（不包含），从1开始计算
    endPos: number;   // 结束位置(不包含)，从0开始计算，指字符位置，不是字节位置
};

/**
 * html 标签 属性
 */
export class TagAttr {
    public attrs: {[name:string]:{
        value?: string;   // 标签属性值
        nameLocation?: TokenLocation; //标签位置
        valueLocation?: TokenLocation;// 标签属性位置        
    }};    // 标签属性
    constructor( attrs?:{[name:string]:string|undefined}) {
        this.attrs = {};
        if(attrs){this.setAttrs(attrs);}
    }
    /**
     * 获取一个 属性值
     * @param name 
     * @returns 
     */
    getAttr(name:string):string|undefined{
        const a = this.attrs[name];
        if(a){return a.value;}
    }
    /**
     * 设置一个 属性
     * @param name 
     * @param value 
     * @param nameLocation 
     * @param valueLocation 
     */
    setAttr(name:string,value?:string,nameLocation?: TokenLocation,valueLocation?: TokenLocation){
        if(this.attrs[name] === undefined && (value||nameLocation||valueLocation)){
            this.attrs[name] ={};
        }
        if(value) this.attrs[name].value = value;
        if(nameLocation) this.attrs[name].nameLocation = nameLocation;
        if(valueLocation) this.attrs[name].valueLocation = valueLocation;
    }
    /**
     * 设置多个标签属性
     * @param attrs 
     */
    setAttrs(attrs:{[name:string]:string|undefined}|undefined){
        for(let name in attrs){
            this.attrs[name] = {value:attrs[name]};
        }
    }
    /**
     * 检查属性
     * @param attrs 
     * @returns 
     */
    checkAttrs(attrs:{[name:string]:string|undefined}|undefined):boolean{
        for(let name in attrs){
            if(this.attrs[name] === undefined || (attrs[name] !== undefined && this.attrs[name] != attrs[name])){
                return false;
            }
        }
        return true;
    }
    forEachAttr(callback:(attr:string, value:string|undefined)=>boolean|void):void{
        for(let n in this.attrs){
            if(false == callback(n,this.attrs[n].value)){
                break;
            }
        }
    }
    toString():string{
        let _attrs = " ";
        for(const name in this.attrs){
            const value = this.attrs[name].value;
            if(value){
                _attrs += `${name}="${value}" `;
            }else {
                _attrs+= (name + ' ');
            }
        }
        if(_attrs.length){return _attrs.trimEnd();}
        return "";
    }
    reset(){
        this.attrs ={}
    }
    isEmpty():boolean{
        return !Boolean(Object.keys(this.attrs).length) ;
    }
};

enum TagType {
    "#root", "#text", "#comment", "script", "template", "style"
}

enum TagStage {
    STag = 0,
    STagName = 1,
    STagAttr = 2, //开始标签属性
    STagEnd = 3, // 开始标签关闭
    STagClose = 4, // 开始标签自关闭 例:<br />
    ETag = 5, // 关闭标签
    ETagName = 6,
    ETagEnd = 7,
    Text = 8, //仅为文本
    ErrEtagEnd = -1, // 关闭标签错误,例: </div a>
    ErrStagClose = -2, // 自关闭标签错误,例:<br / >
    Err = -10, //基它错误
    ErrStag = -3,
};

/**
 * HTML 解析器
 */
export class HtmlTag {
    public type: TagType | string; // 标签类型
    public value?: string; // 当type为 "#text" 时数据保存在此处// 位置保存在 contentLocation之中
    public attrs?: TagAttr;
    public children: HtmlTag[];
    public parent: HtmlTag;
    public contentLocation?: TokenLocation; //内容的位置
    public location: TokenLocation; // 开始标签到结束的位置，当为 "#root"|"#text"|"#comment"|"script"|"style" 没有
    public namespaceURL?: 'http://www.w3.org/1999/xhtml' | //命名空间
        'http://www.w3.org/1998/Math/MathML' |
        'http://www.w3.org/2000/svg' |
        'http://www.w3.org/1999/xlink' |
        'http://www.w3.org/XML/1998/namespace' |
        'http://www.w3.org/2000/xmlns/';
    constructor(parent: HtmlTag, type: TagType | string, attrs?: {[name:string]:string|undefined}, pos?: number, line?: number, col?: number) {
        this.parent = parent;
        if (!parent.children.includes(this)) { parent.children.push(this); }
        this.type = type;
        if(attrs) {this.attrs = new TagAttr(attrs);}
        this.children = [];
        this.location = { startPos: pos, startLine: line, startCol: col } as TokenLocation;
    }
    /**
     * 获取子元素中第一个符合要求的 tag, 如果参数均为 undefined,返回第一个子元素
     * @param tagName 
     * @param tagAttr 
     * @returns 
     */
    queryChild(tagName: string | undefined, tagAttrs?: {[name:string]:string|undefined}):HtmlTag | undefined {
        for (let i = 0; i != this.children.length; i++) {
            if ((tagName === undefined || tagName == this.children[i].type) && this.children[i].checkAttrs(tagAttrs)) {
                return this.children[i];
            }
        }
    }
    queryChildren(tagName: string | undefined, tagAttrs?: {[name:string]:string}|undefined):HtmlTag[]{
        let l:HtmlTag[] = [];
        for (let i = 0; i != this.children.length; i++) {
            if ((tagName === undefined || tagName == this.children[i].type) && this.children[i].checkAttrs(tagAttrs)) {
                l.push(this.children[i]) ;
            }
        }
        return l;
    }
    /**
     * 获取包括自身的后代元素中第一个符合要求的 tag, 如果参数均为 undefined,返回第一个子元素
     * @param tagName 
     * @param tagAttrs 
     */
    queryDescendant(tagName: string | undefined, tagAttrs?: {[name:string]:string|undefined} ):HtmlTag | undefined{
        let stack:HtmlTag[] = [this];
        // 树结构中的层序遍历 @see https://www.geeksforgeeks.org/level-order-tree-traversal/
        while(1){
            let stack2:HtmlTag[] = [];
            for(let i = 0; i != stack.length; i++){
                if ((tagName === undefined || tagName == stack[i].type) && stack[i].checkAttrs(tagAttrs)) {
                    return stack[i];
                }
                for(let j = 0; j != stack[i].children.length; j++){
                    stack2.push(stack[i].children[j]);
                }
            }
            if(stack2.length){stack = stack2;}else{break;}
        }
    }
    queryDescendants(tagName: string | undefined, tagAttrs?: {[name:string]:string|undefined}):HtmlTag[]{
        let l:HtmlTag[] = [];
        let stack:HtmlTag[] = [this];
        // 树结构中的层序遍历 @see https://www.geeksforgeeks.org/level-order-tree-traversal/
        while(1){
            let stack2:HtmlTag[] = [];
            for(let i = 0; i != stack.length; i++){
                if ((tagName === undefined || tagName == stack[i].type) && stack[i].checkAttrs(tagAttrs)) {
                    l.push(stack[i]);
                }
                for(let j = 0; j != stack[i].children.length; j++){
                    stack2.push(stack[i].children[j]);
                }
            }
            if(stack2.length){stack = stack2;}else{break;}
        }
        return l;
    }
    /**
     * 查找元素
     * @param tagName 
     * @param tagAttrs 
     * @returns 
     */
    queryTag(tagName: string | undefined, tagAttrs: {[name:string]:string} | undefined): HtmlTag | undefined {
        let f = function (tag: HtmlTag, tagName: string | undefined, tagAttrs: {[name:string]:string} | undefined): HtmlTag | undefined {
            if ((tagName === undefined || tagName == tag.type) && tag.checkAttrs(tagAttrs)) {
                return tag;
            }
            for (let i = 0; i != tag.children.length; i++) {
                let t = f(tag.children[i], tagName, tagAttrs) as HtmlTag;
                if (t) {
                    return t;
                }
            }
        }
        return f(this, tagName, tagAttrs);
    }
    /**
     * 检查属性是否符合要求,符合返回 true ,当 tagAttrs 为 undefined 时返回 true
     * @param tagAttrs 
     * @returns 
     */
    checkAttrs(attrs: {[name:string]:string|undefined}|undefined): boolean {
        if((this.attrs === undefined && attrs !== undefined) || (this.attrs?.checkAttrs(attrs) == false) ){
            return false;
        }
        return true;
    }
    /**
     * 设置 tag 的属性,注意,在此允许重复,但是浏览器解析时只认第一个
     * @param tagAttrs 
     * @returns 
     */
    setAttrs(tagAttrs: {[name:string]:string|undefined}|undefined): HtmlTag {
        if(tagAttrs && this.attrs === undefined) {this.attrs = new TagAttr}
        this.attrs?.setAttrs(tagAttrs);
        return this;
    }
    setAttr(name:string,value:string|undefined): HtmlTag {
        if(name && this.attrs === undefined) {this.attrs = new TagAttr}
        this.attrs?.setAttr(name,value);
        return this;
    }
    /**
     * 
     * @param attrName 
     * @returns 
     */
    getAttr(attrName: string): string | undefined {
        if(this.attrs){return this.attrs.getAttr(attrName);}
    }
    /**
     * 添加一个子元素,并返回子元素
     * @param tag 
     * @param attrs 
     * @returns 
     */
    addChild(tag: string | HtmlTag, attrs?: {[name:string]:string|undefined}) {
        let c: HtmlTag = (tag.constructor == String) ? new HtmlTag(this, tag, attrs) : (tag as HtmlTag);
        return c;
    }
    /**
     * 移除子元素
     * @param tag 
     */
    removeChild(tag:HtmlTag){
        for(let i = 0; i != this.children.length; i++){
            if(this.children[i] == tag){
                this.children.splice(i,1);break;
            }
        }
    }
    /**
     * 获取子元素序号
     * @param tag 
     * @returns 
     */
    indexOfChild(tag:HtmlTag){
        for(let i = 0; i != this.children.length; i++){
            if(this.children[i] == tag){
                return i;
            }
        }
        return -1;
    }
    /**
     * 添加多个子元素,返回子元素数量
     * @param tags 
     * @returns 
     */
    addChildren(tags: HtmlTag[]): number {
        for (let i = 0; i != tags.length; i++) { tags[i].parent = this; }
        this.children = this.children.concat(tags);
        return this.children.length;
    }
    /**
     * 将当前 tag 转换为 html 的字符串
     * @returns 
     */
    toString(ss:string=" ",ll:number=-1) {
        if(ll<-1){ll=-1;}
        // 处理属性
        let g = (attrs?:TagAttr):string => { return attrs?.toString()??"" }
        // 处理 子元素
        let h = (children: HtmlTag[] | undefined) => {
            let _children = '';
            if (children) {
                for (let i = 0; i != children.length; i++) {
                    _children += f(children[i]);
                }
            }
            return _children;
        }
        // 处理 tag
        let f = (tag: HtmlTag) => {
            if (tag.type == "#text") {
                return tag.value as string;
            } else if (tag.type == "#comment") {
                return `<!--${tag.value}-->`;
            } else if (tag.type == "#root") {
                return h(tag.children);
            }
            ll++
            let _html = `${(ss.length?"\n":"")+ss.repeat(ll)}<${tag.type}${g(tag.attrs)}>`;
            _html += h(tag.children);
            _html += `${tag.children.length &&ss.length?"\n":""}${tag.children.length?ss.repeat(ll):""}</${tag.type}>`;
            ll--;
            return _html;
        }
        return f(this);
    }
    script(){
        let s ="";
        for (let i = 0; i != this.children.length; i++) {
            if ( this.children[i].type == "script" && this.children[i].children.length && this.children[i].children[0].type == "#text") {
                s += this.children[i].children[0].value as string;
            }
        }
        return s;
    }
    css(){
        let c ="";
        for (let i = 0; i != this.children.length; i++) {
            if ( this.children[i].type == "style" && this.children[i].children.length && this.children[i].children[0].type == "#text") {
                c += this.children[i].children[0].value as string;
            }
        }
        return c;
    }
    /**
     * 
     * @returns 
     */
    template(){
        let t = "";
        this.forEach((c,i,ch)=>{
            if(c.type == "template"){
                t += c.toString("");
            }
        })
        return t;
    }
    /**
     * 遍历所有子元素，当返回 false 终止遍历
     */
    forEach(callback:(child:HtmlTag, index:number,children:HtmlTag[])=>boolean|void):void{
        for(let i = 0; i != this.children.length; i++){
            if(false == callback(this.children[i],i,this.children)){break;}
        }
    }
    /**
     * 遍历所有后代元素
     * @param tagName 
     * @param tagAttrs 
     * @returns 
     */
    forEachDescendant(callback:(descendant:HtmlTag, index:number,level:number)=>boolean|void, tagName?: string, tagAttrs?:{[name:string]:string|undefined}):void{
        let stack:HtmlTag[] = [this];
        // 树结构中的层序遍历 @see https://www.geeksforgeeks.org/level-order-tree-traversal/
        let level = 1;
        while(1){
            let stack2:HtmlTag[] = [];
            for(let i = 0; i != stack.length; i++){
                if ((tagName === undefined || tagName == stack[i].type) && stack[i].checkAttrs(tagAttrs)) {
                    if( false == callback(stack[i],stack[i].parent.indexOfChild(stack[i]),level) ){return;}
                }
                for(let j = 0; j != stack[i].children.length; j++){
                    stack2.push(stack[i].children[j]);
                }
            }
            if(stack2.length){stack = stack2;level++;}else{break;}
        }
    }
    /**
     * 遍历所有属性
     * @param callback 
     */
    forEachAttr(callback:(attr:string, value:string|undefined)=>boolean|void):void{
        this.attrs?.forEachAttr(callback);
    }
    /**
     * 
     * @param tagName +
     * @returns 
     */
    static createRoot(tagName?: string) {
        let ht = new HtmlTag({ children: [] } as unknown as HtmlTag, tagName ? tagName : "#root", undefined, 0, 1, 1);
        //@ts-ignore
        delete ht.parent;
        return ht;
    }
    /**
     * 从字符串 fragment 生成 HtmlTag
     * @param fragment 
     * @param tagParent 
     * @param pos 
     * @param line 
     * @param col 
     * @returns 
     */
    static fromString(fragment: string, tagParent?: HtmlTag): HtmlTag | undefined {
        let root = tagParent !== undefined ? tagParent : (HtmlTag.createRoot());
        let stack: HtmlTag[] = [root]; //确保肯定能取出 HtmlTag
        let pos: number = 0, line: number = 1, col: number = 1;
        while (pos < fragment.length) {
            let stackTop = stack.at(-1) as HtmlTag;
            let pos0 = pos, line0 = line, col0 = col;
            [pos, line, col] = HtmlTag.findChar(fragment, 60/* 符号 '<' */, pos, line, col); // 查找 '<' 符号 pos 为 < 开始的地方
            if (pos == -1) {
                return root;
            } else if (pos > pos0) {
                let v = fragment.substring(pos0, pos).trim(); // 过滤掉仅有空白字符的情况，仅有空白字符不产生 "#text" 结点
                if(v.length){
                    let h = new HtmlTag(stackTop, "#text", undefined, pos0, line0, col0);
                    h.value = fragment.substring(pos0, pos);
                    h.location.endPos = pos; h.location.endLine = line; h.location.endCol = col;
                }
                pos0 = pos - 1, line0 = line, col0 = col;
            }
            let tagName: string | undefined;
            let isEndTag: boolean = false;  //分析是否是关闭标签
            if (47 == fragment.charCodeAt(++pos)) { // 符号 < 后跟 / 是关闭标签
                isEndTag = true; pos++; col += 2;
            } else {
                col++;
            }
            [tagName, pos, line, col] = HtmlTag.getName(fragment, pos, line, col);
            if (!tagName) {
                if (33 == fragment.charCodeAt(pos) && 45 == fragment.charCodeAt(pos + 1) && 45 == fragment.charCodeAt(pos + 1)) { // 处理 注释
                    pos = fragment.indexOf("-->", pos + 3);
                    if (pos == -1) {
                        return undefined;
                    }
                    pos += 3;
                    {//校正 line col
                        let p = pos0, c = col0;
                        while ((p = fragment.indexOf("\n", p)), p > 0 && p < pos) {
                            p++; line++; c = p;
                        }
                        col = pos - c;
                    }
                    let h = new HtmlTag(stackTop, "#comment", undefined, pos0, line0, col0);
                    h.value = fragment.substring(pos0 + 4, pos - 3);
                    h.location.endPos = pos; h.location.endLine = line; h.location.endCol = col;
                    h.contentLocation = { startPos: pos0 + 4, startLine: line0, startCol: col0 + 3, endPos: pos - 3, endLine: line, endCol: col, };
                    continue;
                }
                return undefined;
            }
            if (isEndTag) {
                [pos, line, col] = HtmlTag.skipSpace(fragment, pos, line, col);
                if (62 != fragment.charCodeAt(pos)) { // ETag 关闭
                    return undefined;
                } else {
                    pos++; col++;
                    if (tagName != stackTop.type) {
                        return undefined; // STag 与 ETag不一致
                    }
                    stackTop.location.endPos = pos; stackTop.location.endLine = line; stackTop.location.endCol = col;
                    //@ts-ignore
                    stackTop.contentLocation.endPos = pos0; stackTop.contentLocation.endLine = line0; stackTop.contentLocation.endCol = col0;
                    stack.pop(); // 弹出已关闭的标签
                    continue;
                }
            }
            let tagStage = 0;
            let ht = new HtmlTag(stackTop, tagName, undefined, pos0, line0, col0); //是新的STag
            [pos, line, col, tagStage] = HtmlTag.getTagAttr(fragment, ht, pos, line, col);
            switch (tagStage) {
                case TagStage.STagClose:// 标签自关闭
                    ht.location.endPos = pos; ht.location.endLine = line; ht.location.endCol = col;
                    continue;
                case TagStage.STagEnd:// STag 关闭
                    ht.contentLocation = { startPos: pos, startLine: line, startCol: col } as TokenLocation;//内容定位
                    stack.push(ht);
                    continue;
                case TagStage.Err: {
                    return undefined;
                }
            }
        }
        return root;
    }
    /**
     * 
     * @param fragment 
     * @returns 
     */
    static skipDocType(fragment: string) {
        // 跳过 <!DOCTYPE html>声明
        let pos = 0, line = 1, col = 1;
        [pos, line, col] = HtmlTag.skipSpace(fragment, pos, line, col);
        if (pos == fragment.indexOf("<!DOCTYPE ")) {
            [pos, line, col] = HtmlTag.skipSpace(fragment, pos, line, col + 10);
            if (pos == fragment.indexOf("html")) {
                [pos, line, col] = HtmlTag.findChar(fragment, 62, pos, line, col + 4);
            }
        }
        return [pos, line, col];
    }
    /**
     * 分析字符串 fragment 跳过 空白字符,空白字符定义见: https://www.w3.org/TR/xml/#NT-S
     * 	S ::= (#x20 | #x9 | #xD | #xA)+
     * @param fragment 
     * @param pos 开始分析的位置,从 0 开始,不包含 BOM
     * @param line 开始分析的行号,从 1 开始
     * @param col 开始分析的行内位置,从 1 开始
     * @returns 新的 [pos,line,col]
     */
    private static skipSpace(fragment: string, pos: number, line: number, col: number): [number, number, number] {
        while (pos < fragment.length) {
            let cc = fragment.charCodeAt(pos);
            if (cc == 10) {// 是'\n'.charCodeAt() == 10
                line++; col = 0;
            } else {
                let sign = 0;
                for (let i = 0; i != WhiteSpace.length; i++) {
                    if (cc == WhiteSpace[i]) {
                        sign = 1; break;
                    }
                }
                if (sign) { // 有空白字符
                    col++;
                }
                else { // 遇到回空白字符
                    break;
                }
            }
            pos++;
        }
        return [pos, line, col];
    }
    /**
     * 查找某一字符,并处理 行数及行内位置
     * @param ch 
     * @param pos0 开始位置
     * @param line0  开始位置行号
     * @param col0   开始位置 行内位置
     * @returns []
     */
    private static findChar(fragment: string, ch: number, pos0: number, line0: number, col0: number): [number, number, number] {
        let cnt = 0;
        const lf = '\n'.charCodeAt(0);
        let line = line0;
        let col = col0;
        let pos1 = fragment.length;
        for (let i = pos0; i < pos1; i++) {
            let cp = fragment.charCodeAt(i);
            if (ch == cp) {
                return [i, line, col]; // 成功返回新的位置，新的行号，行内位置
            }
            if (10 == cp) { //与 \n做比较
                line++; col = 1;
            }
            else {
                col++;
            }

        }
        return [-1, line0, col0]; //失败返回 位置为-1，和原来的行号，行内位置
    }
    /**
     *  私有函数，检查 pos 位置的字符是否是 NameStartChar 见 https://www.w3.org/TR/xml/#NT-Name 
        https://www.w3.org/TR/xml/#NT-Name  关于html 标签允许的字符定义
        [4]   	NameStartChar	   ::=   	":" | [A-Z] | "_" | [a-z, 0xC0,0xD6, 0xD8,0xF6, 0xF8,0x2FF] | 
            [#x370,0x37D, 0x37F,0x1FFF, 0x200C,0x200D, 0x2070,0x218F, 0x2C00,0x2FEF, 0x3001,0xD7FF] | 
            [#xF900,0xFDCF, 0xFDF0,0xFFFD, 0x10000,0xEFFFF]
        [4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300,0x036F, 0x203F,0x2040]
        [5]   	Name	   ::=   	NameStartChar (NameChar)*
        //  32    33 !  34 "  35 #  36 $  37 %  38 &  39 '  40 (  41 )  42 *  43 +  44 ,  45 -  46 .  47 /  
        //  48 0  49 1  50 2  51 3  52 4  53 5  54 6  55 7  56 8  57 9  58 :  59 ;  60 <  61 =  62 >  63 ?  
        //  64 @  65 A  66 B  67 C  68 D  69 E  70 F  71 G  72 H  73 I  74 J  75 K  76 L  77 M  78 N  79 O  
        //  80 P  81 Q  82 R  83 S  84 T  85 U  86 V  87 W  88 X  89 Y  90 Z  91 [  92 \  93 ]  94 ^  95 _  
        //  96 `  97 a  98 b  99 c 100 d 101 e 102 f 103 g 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o 
        //  112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w 120 x 121 y 122 z 123 { 124 | 125 } 126 ~
     * @param fragment 
     * @param pos 
     */
    private static checkNameStartChar(fragment: string, pos: number): boolean {
        // 合法的 html标签开始字符,奇数为开始,偶数为结束,包含开始和结束
        let ch = fragment.charCodeAt(pos);
        if (ch != undefined) {
            for (let i = 0; i != NameStartChar.length; i += 2) {
                if (ch >= NameStartChar[i] && ch <= NameStartChar[i + 1]) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 私有函数，检查 pos 位置的字符是否是 NameChar 见 https://www.w3.org/TR/xml/#NT-Name 
     * @param pos 
     * @returns 
     */
    private static checkNameChar(fragment: string, pos: number): boolean {
        // 合法的 html标签字符,奇数为开始,偶数为结束,包含开始和结束
        let ch = fragment.charCodeAt(pos);
        if (ch != undefined) {
            for (let i = 0; i != NameChar.length; i += 2) {
                if (ch >= NameChar[i] && ch <= NameChar[i + 1]) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 从 pos0处开始 获取 Tag的标签或 Tag 的标签的属性名
     */
    private static getName(fragment: string, pos: number, line: number, col: number): [string | undefined, number, number, number] {
        if (HtmlTag.checkNameStartChar(fragment, pos)) {
            for (let i = pos + 1; i != fragment.length; i++) {
                if (!HtmlTag.checkNameChar(fragment, i)) {
                    return [fragment.substring(pos, i), i, line, col + i - pos];
                }
            }
        }
        return [undefined, pos, line, col];
    }
    /**
     * 获得取html 的 属性
     * @param aph 当前准备获取属性的标签对象
     * @param pos 
     * @param line 
     * @param col 
     * @returns
     * 
     */
    private static getTagAttr(fragment: string, ht: HtmlTag, pos: number, line: number, col: number): [number, number, number, TagStage] {
        let tagStage: TagStage = TagStage.Err; // 标签状态
        let attrName: string | undefined;
        let attrs = new TagAttr;
        // see https://www.w3.org/TR/xml/#NT-STag
        while (pos < fragment.length) {
            [pos, line, col] = HtmlTag.skipSpace(fragment, pos, line, col);
            let pos0 = pos, line0 = line, col0 = col;
            [attrName, pos, line, col] = HtmlTag.getName(fragment, pos, line, col);
            let pos1 = pos, line1 = line, col1 = col;
            [pos, line, col] = HtmlTag.skipSpace(fragment, pos, line, col); // = 之前 跳过空白字符
            let cc = fragment.charCodeAt(pos++);
            if (attrName) {
                if (61 == cc) { //  是 '=' 
                    [pos, line, col] = HtmlTag.skipSpace(fragment, pos, line, col + 1);// = 之后 跳过空白字符
                    let qq = fragment.charCodeAt(pos);
                    if (qq == 34 || qq == 39) { // 是 '\"' 或 '\''
                        let pos2 = ++pos, line2 = line, col2 = ++col;
                        let pos3: number | undefined;
                        while (pos < fragment.length) {
                            let qq2 = fragment.charCodeAt(pos);
                            if (qq2 == 10) { line++; } else if (qq2 == 60) { break; } else if (qq == qq2) { pos3 = pos++; break; }
                            pos++;
                        }
                        if (pos3 !== undefined) {
                            attrs.setAttr(attrName,fragment.substring(pos2, pos3),
                                { startPos: pos0, startLine: line0, startCol: col0, endPos: pos1, endLine: line1, endCol: col1 },
                                { startPos: pos2, startLine: line2, startCol: col2, endPos: pos3, endLine: line, endCol: col2 + pos3 - pos2 });
                        } else {
                            return [pos, line, col, TagStage.Err];
                        }
                        col = col2 + pos3 - pos2 + 1;
                        continue;
                    }
                } else {
                    if (47 == cc) { // 是 '/'
                        if (62 != fragment.charCodeAt(pos++)) {
                            return [pos, line, col, TagStage.Err];
                        }
                        tagStage = TagStage.STagClose;
                    } else if (62 == cc) { // 是 '>'
                        tagStage = TagStage.STagEnd;
                    }
                    attrs.setAttr(attrName,undefined,
                        { startPos: pos0, startLine: line0, startCol: col0, endPos: pos1, endLine: line1, endCol: col1 },);
                    if (tagStage != TagStage.Err) { break; }
                    continue;
                }
            }
            if (47 == cc) { // 是 '/'
                if (62 != fragment.charCodeAt(pos++)) {
                    return [pos, line, col + 1, TagStage.Err];
                }
                tagStage = TagStage.STagClose; col++; break;
            } else if (62 == cc) { // 是 '>'
                tagStage = TagStage.STagEnd; col++; break;
            } else {
                break;
            }
        }
        if (!attrs.isEmpty()) { ht.attrs = attrs; }
        return [pos, line, col, tagStage];
    }
}

enum CssType {
    Comment = "#comment", // 注释

}
/**
 * CSS 解析器
 */
export class CssTag {
    public type?: CssType;
    public selector: string[];
    public properties: { [key: string]: string | CssTag }
    constructor() {
        this.selector = [];
        this.properties = {};
    }
    /**
     * 从 CssTag 生成 css 文本
     * @returns 
     */
    static toString(): string {
        return ""
    }
    /**
     * 从 css 文本生成 CssTag
     * @param fragment 
     */
    static fromString(fragment: string): CssTag {
        return new CssTag;
    }

}